﻿//
// This file is part of AVSReflector project
// Copyright (C) 2011 Yuan Pengfei
//

using System;
using System.Collections.Generic;
using System.Text;

namespace AVSReflector.Core
{
    public class Sequence : INode
    {
        public uint profile_id;
        public uint level_id;
        public uint progressive_sequence;
        public uint horizontal_size;
        public uint vertical_size;
        public uint chroma_format;
        public uint aspect_ratio;
        public uint frame_rate_code;
        public uint bit_rate;
        public uint low_delay;
        public uint bbv_buffer_size;
        public uint mb_width;
        public uint mb_height;

        public INode[] children;

        public string[] GetInfo()
        {
            List<string> lines = new List<string>();
            lines.Add("profile_id = " +profile_id);
            lines.Add("level_id = " +level_id);
            lines.Add("progressive_sequence = " +progressive_sequence);
            lines.Add("horizontal_size = " +horizontal_size);
            lines.Add("vertical_size = " +vertical_size);
            lines.Add("chroma_format = " +chroma_format);
            lines.Add("aspect_ratio = " +aspect_ratio);
            lines.Add("frame_rate_code = " +frame_rate_code);
            lines.Add("bit_rate = " +bit_rate);
            lines.Add("low_delay = " +low_delay);
            lines.Add("bbv_buffer_size = " +bbv_buffer_size);
            lines.Add("mb_width = " +mb_width);
            lines.Add("mb_height = " +mb_height);
            return lines.ToArray();
        }

        public static Sequence Parse(BitStream s)
        {
            Sequence ret = new Sequence();
            Sequence bSequence = Context.currentSequence;
            List<INode> l = new List<INode>();
            s.escape = false;
            if (!s.SkipToCode(0x1b0, 32))
                return null;
            s.Read(32);

            // Parse sequence header
            ret.profile_id = s.Read(8);
            // TODO: Add more profile IDs' support
            if (ret.profile_id != 0x20)
                throw new Exception("Profile ID not supported: " + ret.profile_id);
            ret.level_id = s.Read(8);
            ret.progressive_sequence = s.Read(1);
            ret.horizontal_size = s.Read(14);
            ret.vertical_size = s.Read(14);
            ret.chroma_format = s.Read(2);
            s.Read(3);
            ret.aspect_ratio = s.Read(4);
            ret.frame_rate_code = s.Read(4);
            ret.bit_rate = s.Read(18);
            s.Read(1);
            ret.bit_rate += s.Read(12) << 18;
            // TODO: Add low delay support
            ret.low_delay = s.Read(1);
            if (ret.low_delay == 1)
                throw new Exception("Low delay not supported!");
            s.Read(1);
            ret.bbv_buffer_size = s.Read(18);
            s.Read(3);
            ret.mb_width = (ret.horizontal_size + 15) / 16;
            if (ret.progressive_sequence == 0)
                ret.mb_height = 2 * ((ret.vertical_size + 31) / 32);
            else
                ret.mb_height = (ret.vertical_size + 15) / 16;
            s.SkipToCode(0x1, 24);

            // Parse extension & user data
            while (true)
            {
                uint t = s.Peek(32);
                if (t == 0x1b5)
                {
                    s.Read(32);
                    INode o = null;
                    switch (s.Peek(4))
                    {
                        case 0x2:
                            s.Read(4);
                            o = SequenceDisplayExtension.Parse(s);
                            break;
                        case 0x4:
                            s.Read(4);
                            o = CopyrightExtension.Parse(s);
                            break;
                        case 0xb:
                            s.Read(4);
                            o = CameraParametersExtension.Parse(s);
                            break;
                        default:
                            s.SkipToCode(0x1, 24);
                            break;
                    }
                    if (o != null)
                        l.Add(o);
                }
                else if (t == 0x1b2)
                {
                    s.Read(32);
                    l.Add(UserData.Parse(s));
                }
                else
                    break;
            }

            // Parse pictures
            Context.currentSequence = ret;
            while (true)
            {
                if (s.EndOfStream())
                    break;
                uint t = s.Peek(32);
                if (t == 0x1b3)
                {
                    s.Read(32);
                    l.Add(IPicture.Parse(s));
                }
                else if (t == 0x1b6)
                {
                    s.Read(32);
                    uint bbv_delay = s.Read(16);
                    uint type = s.Read(2);
                    if (type == 1)
                    {
                        PPicture p = PPicture.Parse(s);
                        p.bbv_delay = bbv_delay;
                        l.Add(p);
                    }
                    else if (type == 2)
                    {
                        BPicture p = BPicture.Parse(s);
                        p.bbv_delay = bbv_delay;
                        l.Add(p);
                    }
                    else
                        throw new Exception("Picture coding type not supported: " + type);
                }
                else
                    break;
            }

            /*while (!s.EndOfStream() && s.Peek(32) == 0x1b0)
            {
                Sequence r = Sequence.Parse(s);
                if (r != null)
                    l.Add(r);
            }

            if (withEnding)
            {
                s.SkipToCode(0x1b1, 32);
                s.Read(32);
            }*/
            ret.children = l.ToArray();
            Context.currentSequence = bSequence;
            return ret;
        }
    }
}
